package jmine.tec.sync;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import jmine.tec.persist.naturalkey.NaturalKeyHandler;
import jmine.tec.persist.naturalkey.NaturalKeyHandlerFactory;
import jmine.tec.rpc.sync.DatabaseSynchronizationCommand;
import jmine.tec.rpc.sync.Externalizable;

import org.hibernate.Hibernate;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import bancosys.tec.persist.persister.listener.AbstractPersisterListener;
import bancosys.tec.persist.persister.listener.PersisterListener;

/**
 * Persister listener
 * 
 * @author takeshi
 */
public class DataSynchronizationPersisterListener extends AbstractPersisterListener<Externalizable> implements
        PersisterListener<Externalizable> {

    private DataPushChannel channel;

    private NaturalKeyHandlerFactory naturalKeyHandlerFactory;

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterInsert(Externalizable bean, Object action) {
        NaturalKeyHandler handler = this.getNaturalKeyHandlerFactory().findNaturalKeyProperties(bean.getClass());
        if (handler == null) {
            this.getCurrentTransactionSynchronization().addCommand(new EntryUpdatedCommand(this.extractClassHierarchy(bean), bean.getId()));
        } else {
            this.getCurrentTransactionSynchronization().addCommand(
                    new EntryUpdatedCommand(this.extractClassHierarchy(bean), bean.getId(), handler.getNaturalKey(bean)));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterRemove(Externalizable bean) {
        NaturalKeyHandler handler = this.getNaturalKeyHandlerFactory().findNaturalKeyProperties(bean.getClass());
        if (handler == null) {
            this.getCurrentTransactionSynchronization().addCommand(new EntryDeleteCommand(this.extractClassHierarchy(bean), bean.getId()));
        } else {
            Serializable naturalKey = handler.getNaturalKey(bean);
            this.getCurrentTransactionSynchronization().addCommand(
                    new EntryDeleteCommand(this.extractClassHierarchy(bean), bean.getId(), naturalKey));
        }
    }

    /**
     * Extrai a hierarquia de classes do objeto passado em forma de um array de String
     * 
     * @param object Object
     * @return array de String
     */
    private String[] extractClassHierarchy(Object object) {
        List<String> list = new ArrayList<String>();
        Class<?> type = Hibernate.getClass(object);
        while (type != Object.class) {
            list.add(type.getName());
            type = type.getSuperclass();
        }
        return list.toArray(new String[list.size()]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterUpdate(Externalizable bean, Object action) {
        this.afterInsert(bean, action);
    }

    /**
     * Devolve o {@link PushChangesTransactionSynchronization} registrado para a {@link Thread} atual
     * 
     * @return {@link PushChangesTransactionSynchronization}
     */
    private PushChangesTransactionSynchronization getCurrentTransactionSynchronization() {
        PushChangesTransactionSynchronization txSync = TX_SYNC.get();
        if (txSync == null) {
            txSync = new PushChangesTransactionSynchronization();
            TransactionSynchronizationManager.registerSynchronization(txSync);
        }
        return txSync;
    }

    public static final ThreadLocal<PushChangesTransactionSynchronization> TX_SYNC =
            new ThreadLocal<PushChangesTransactionSynchronization>();

    /**
     * {@link TransactionSynchronization} responsavel por 'empurrar' mudancas para os clientes quando o commit eh bem sucedido. Esta classe
     * NAO deve ser usada por multiplas threads em paralelo!
     * 
     * @author takeshi
     */
    public class PushChangesTransactionSynchronization extends TransactionSynchronizationAdapter implements TransactionSynchronization {

        private List<DatabaseSynchronizationCommand> commands = new LinkedList<DatabaseSynchronizationCommand>();

        /**
         * {@inheritDoc}
         */
        @Override
        public void afterCompletion(int status) {
            if (!this.commands.isEmpty() && (DataSynchronizationPersisterListener.this.channel != null)
                    && (status == TransactionSynchronization.STATUS_COMMITTED)) {
                try {
                    DataSynchronizationPersisterListener.this.getChannel().pushCommands(
                            this.commands.toArray(new DatabaseSynchronizationCommand[this.commands.size()]));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * Adiciona comandos
         * 
         * @param cmd {@link DatabaseSynchronizationCommand}
         */
        public void addCommand(DatabaseSynchronizationCommand cmd) {
            this.commands.add(cmd);
        }

    }

    /**
     * @return {@link DataPushChannel}
     */
    public DataPushChannel getChannel() {
        return this.channel;
    }

    /**
     * @param channel {@link DataPushChannel}
     */
    public void setChannel(DataPushChannel channel) {
        this.channel = channel;
    }

    /**
     * @return {@link NaturalKeyHandlerFactory}
     */
    public NaturalKeyHandlerFactory getNaturalKeyHandlerFactory() {
        return this.naturalKeyHandlerFactory;
    }

    /**
     * @param naturalKeyHandlerFactory {@link NaturalKeyHandlerFactory}
     */
    public void setNaturalKeyHandlerFactory(NaturalKeyHandlerFactory naturalKeyHandlerFactory) {
        this.naturalKeyHandlerFactory = naturalKeyHandlerFactory;
    }

}
